﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.InteropServices;

using alib;
using alib.Array;
using alib.Character;
using alib.Collections;
using alib.Debugging;
using alib.Enumerable;

namespace agree.rewriter
{
	public interface IRuleElement : ITfsSlot
	{
		ModeIx ModeIx { get; }
		int Index { get; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// recommended base class for context-sensitive rule elements
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//[DebuggerDisplay("{ToString(),nq}", Name = "{ci_index.ToString().PadLeft(2),nq} {mode_ix.ToString(),nq}")]
	//[DebuggerDisplay("{ci_index.ToString().PadLeft(2),nq} {mode_ix.ToString(),nq}")]
	public class Element : TfsSlotRef, IRuleElement
	{
		public enum Mode : byte
		{
			CONTEXT = 0,
			INPUT = 1,
			OUTPUT = 2,
			ERROR = 0xF,
		};

		Element(Tfs tfs, int ix1, ModeIx mi, int ci_index)
			: base(tfs, ix1)
		{
			this.mode_ix = mi;
			this.ci_index = ci_index;
		}
		public Element(TfsSlot ts, ModeIx mi, int ci_index)
			: this(ts.tfs, ts.ix1, mi, ci_index)
		{
		}
		public Element(TfsSlot ts, Mode mode, int element_index, int ci_index)
			: this(ts, new ModeIx(mode, element_index), ci_index)
		{
		}
		public Element(ITfsSlot ts, ModeIx mi, int ci_index)
			: this(ts.Tfs, ts.SlotIndex, mi, ci_index)
		{
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		readonly ModeIx mode_ix;
		public ModeIx ModeIx { [DebuggerStepThrough] get { return mode_ix; } }

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		readonly int ci_index;
		public int Index { [DebuggerStepThrough] get { return ci_index; } }

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		TfsSlot _for_debug { get { return base.TfsSlot; } }

		public override String ToString()
		{
			return String.Format("{0} {1}", ci_index, mode_ix);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	[StructLayout(LayoutKind.Explicit, Size = 2)]
	public struct ModeIx : IEquatable<ModeIx>
	{
		public static readonly ModeIx Error;

		static ModeIx() { Error = new ModeIx(Element.Mode.ERROR, -1); }

		public static Element.Mode FromChar(Char ch)
		{
			switch (ch)
			{
				case 'C': return Element.Mode.CONTEXT;
				case 'I': return Element.Mode.INPUT;
				case 'O': return Element.Mode.OUTPUT;
				default: return Element.Mode.ERROR;
			}
		}
		public static Char ToChar(Element.Mode mode) { return "CIO"[(byte)mode]; }

		/// <param name="mode">rewriter element mode (CONTEXT, INPUT, OUTPUT, ...)</param>
		/// <param name="index">0-based index of the element in the tfs list</param>
		public ModeIx(Element.Mode mode, int index)
		{
			if (mode > Element.Mode.OUTPUT && mode != Element.Mode.ERROR)
				throw new Exception("Invalid rewriter element mode");
			this._data = 0;
			this.mode = mode;
			this.index = (byte)(uint)index;
		}
		public ModeIx(String s)
			: this(FromChar(s.ToUpper()[0]), int.Parse(s.Substring(1)) - 1)
		{
		}
		[FieldOffset(0)]
		public readonly ushort _data;
		[FieldOffset(0)]
		public readonly byte index;
		[FieldOffset(1)]
		public readonly Element.Mode mode;

		public bool IsValid { get { return index != 0xFF && mode <= Element.Mode.OUTPUT; } }

		public Char ModeChar { get { return ToChar(mode); } }
		public override String ToString()
		{
			if (mode == Element.Mode.ERROR)
				return "ERROR" + (index + 1).ToString();
			return String.Format("{0}{1}", ToChar(mode), (index + 1).ToString());
		}
		public static bool operator ==(ModeIx a, ModeIx b) { return a._data == b._data; }
		public static bool operator !=(ModeIx a, ModeIx b) { return a._data != b._data; }
		public override bool Equals(object obj) { return obj is ModeIx && ((ModeIx)obj)._data == this._data; }
		public bool Equals(ModeIx other) { return this._data == other._data; }
		public override int GetHashCode() { return _data; }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	[StructLayout(LayoutKind.Sequential, Size = 8)]
	public struct PositionalConstraint
	{
		public enum Op : byte /*  */ { PRECEEDS = 0, SUCCEEDS, LEFT_ADJ, RIGHT_ADJ, COVERS };
		static String[] opc = new[] {  /**/"<<", /**/ ">>",/**/ "<", /**/ ">",  /**/ "@" };

		public static IEnumerable<PositionalConstraint> ExtractDuples(String s)
		{
			ModeIx mi1 = ModeIx.Error;
			ModeIx mi2 = ModeIx.Error;
			Op op = (Op)0xFF;

			for (int j = 0; j < s.Length; )
			{
				Char ch;
				if (Char.IsWhiteSpace(ch = s[j++]))
					continue;

				Element.Mode em;
				if ((em = ModeIx.FromChar(ch)) == Element.Mode.ERROR)
				{
					if (!mi2.IsValid)
						throw new Exception();

					if (ch == '<')
					{
						if (j < s.Length && s[j] == '<')
						{
							j++;
							op = Op.PRECEEDS;
						}
						else
							op = Op.LEFT_ADJ;
					}
					else if (ch == '>')
					{
						if (j < s.Length && s[j] == '>')
						{
							j++;
							op = Op.SUCCEEDS;
						}
						else
							op = Op.RIGHT_ADJ;
					}
					else if (ch == '@')
						op = Op.COVERS;
					else
						throw new Exception();

					mi1 = mi2;
					mi2 = ModeIx.Error;
				}
				else
				{
					if (j == s.Length)
						throw new Exception();

					int ix = s[j++] - '1';
					if (ix < 0 || ix > 9)
						throw new Exception();

					int k;
					while (j < s.Length && ((k = s[j] - '1') >= 0 && k <= 9))
					{
						ix = ix * 10 + k;
						j++;
					}

					mi2 = new ModeIx(em, ix);

					if (mi1.IsValid)
					{
						yield return new PositionalConstraint(mi1, op, mi2);
						mi1 = ModeIx.Error;
					}
				}
			}

			if (!mi2.IsValid)
				throw new Exception();
		}

		public PositionalConstraint(ModeIx mi1, Op op, ModeIx mi2)
		{
			// cannot use (e.g.) 'I1@I1' because it is redundant
			if (!mi1.IsValid || !mi2.IsValid || (byte)op >= opc.Length || mi1 == mi2)
				throw new Exception();

			if (op == Op.COVERS)
			{
				// cannot use (e.g.) 'O1@O2' because the output item cannot be anchored
				if (mi1.mode == Element.Mode.OUTPUT && mi2.mode == Element.Mode.OUTPUT)
					throw new Exception();
			}
			else if (op == Op.PRECEEDS || op == Op.SUCCEEDS)
			{
				// cannot use '<<' or '>>' with any OUTPUT element because too many output items 
				// would have to be postulated to cover all leftward (or rightward) spans.
				if (mi1.mode == Element.Mode.OUTPUT || mi2.mode == Element.Mode.OUTPUT)
					throw new Exception();
			}

			if ((this.op = op) == Op.COVERS && mi2.mode == Element.Mode.OUTPUT)
			{
				this.mi1 = mi2;
				this.mi2 = mi1;
			}
			else
			{
				this.mi1 = mi1;
				this.mi2 = mi2;
			}
		}

		public ModeIx mi1, mi2;
		public Op op;

		public unsafe override int GetHashCode()
		{
			var x = this;
			*(int*)&x ^= (byte)op << 4;
			return *(int*)&x;
		}
		public override String ToString() { return mi1.ToString() + opc[(int)op] + mi2.ToString(); }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class RewriteRule<TRuleIn, TRuleOut> : StaticExpandEntry
		where TRuleIn : class, IRuleElement
		where TRuleOut : class, IRuleElement
	{
		public RewriteRule(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}

		protected sealed override Tfs EnsureExpanded()
		{
			var tfs = base.EnsureExpanded();

			AnalyzeRuleBehavior(tfs);

			if (Context == null || Input == null || Output == null)
				throw new Exception("AnalyzeRuleBehavior() did not set a required field.");

			_cache_cross_lists();

			//Console.WriteLine(Name);
			//foreach (var elem in ContextInputOutput)
			//	Console.WriteLine(elem.ToString());

			return tfs;
		}

		protected abstract void AnalyzeRuleBehavior(Tfs tfs);

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public int ContextInputCount
		{
			get { return (Context == null ? 0 : Context.Length) + (Input == null ? 0 : Input.Length); }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public int ElementCount
		{
			get { return ContextInputCount + (Output == null ? 0 : Output.Length); }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public TRuleIn[] Context;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public TRuleIn[] Input;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public TRuleOut[] Output;

		/// synthetic lists:

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		TRuleIn[] _ci;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public TRuleIn[] ContextInput
		{
			get
			{
				var ret = _ci;
				if (ret == null)
				{
					_cache_cross_lists();	// note: not saving results if demanded early
					ret = _ci;
					_ci = null;
				}
				return ret;
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		IRuleElement[] _cio;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IRuleElement[] ContextInputOutput
		{
			get
			{
				var ret = _cio;
				if (_cio == null)
				{
					_cache_cross_lists();	// note: not saving result if demanded early
					ret = _cio;
					_cio = null;
				}
				return ret;
			}
		}

		protected void _cache_cross_lists()
		{
			if (Context == null || Input == null || Output == null)
				return;
			_ci = new TRuleIn[ContextInputCount];
			_cio = new IRuleElement[ElementCount];
			int i, j = 0, k = 0;
			for (i = 0; i < Context.Length; i++)
				_cio[k++] = _ci[j++] = Context[i];
			for (i = 0; i < Input.Length; i++)
				_cio[k++] = _ci[j++] = Input[i];
			for (i = 0; i < Output.Length; i++)
				_cio[k++] = Output[i];
		}

		public IRuleElement this[ModeIx mode_ix]
		{
			get
			{
				switch (mode_ix.mode)
				{
					case Element.Mode.CONTEXT:
						return Context[mode_ix.index];
					case Element.Mode.INPUT:
						return Input[mode_ix.index];
					case Element.Mode.OUTPUT:
						return Output[mode_ix.index];
				}
				throw new Exception();
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		Element[] _for_debug
		{
			get { return ContextInputOutput.Select((e, ix) => e as Element ?? new Element(e, e.ModeIx, ix)).ToArray(); }
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//public abstract class PositionalRewriteRule<TRuleIn, TRuleOut> : RewriteRule<TRuleIn, TRuleOut>
	//	where TRuleIn : IRuleElement
	//	where TRuleOut : IRuleElement
	//{
	//	public PositionalRewriteRule(Grammar g, Type t, ConsDefs consdef)
	//		: base(g, t, consdef)
	//	{
	//		PositionConstraints = alib.Collections.Collection<PositionalConstraint>.None;
	//	}

	//	[DebuggerDisplay("{System.String.Join(\" \",PositionConstraints),nq}")]
	//	public PositionalConstraint[] PositionConstraints;

	//	protected override void AnalyzeRuleBehavior(Tfs tfs)
	//	{
	//		var ps = em.TokenMappingGeometry.ChartMappingPositionPath.GetSlot(tfs).StringValue;
	//		if (ps != null)
	//			PositionConstraints = ps.Split(Charset.comma_ws, StringSplitOptions.RemoveEmptyEntries)
	//									.SelectMany(s => PositionalConstraint.ExtractDuples(s))
	//									.ToArray();

	//		//Console.WriteLine("{0,-15} {1}", this.Name, PositionConstraints.Select(pc => "[" + pc.ToString() + "]").StringJoin(" "));
	//	}
	//};
}
